package com.anji.plus.gaea.job.executor.service;

import com.anji.plus.gaea.job.core.constant.ExecutorBlockStrategyEnum;
import com.anji.plus.gaea.job.core.constant.JobTypeEnum;
import com.anji.plus.gaea.job.core.dto.LogResult;
import com.anji.plus.gaea.job.core.dto.ReturnT;
import com.anji.plus.gaea.job.core.param.IdleBeatParam;
import com.anji.plus.gaea.job.core.param.KillParam;
import com.anji.plus.gaea.job.core.param.LogParam;
import com.anji.plus.gaea.job.core.param.TriggerParam;
import com.anji.plus.gaea.job.executor.handler.JobHandler;
import com.anji.plus.gaea.job.executor.handler.impl.SQLJobHandler;
import com.anji.plus.gaea.job.executor.thread.JobThread;
import com.anji.plus.gaea.job.executor.util.XxlJobFileAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * @desc 执行器提供给调度器远程调用接口
 * @author 木子李*de
 * @date 2023-05-17 15:25:35.961
 *
 * Borrowed from xxljob v2.4.0
 **/
@Service
public class ExecutorService {
    private static Logger logger = LoggerFactory.getLogger(ExecutorService.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public ReturnT<String> beat(){
        return ReturnT.SUCCESS;
    }

    public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam){
        // isRunningOrHasQueue
        boolean isRunningOrHasQueue = false;
        JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatParam.getJobId());
        if (jobThread != null && jobThread.isRunningOrHasQueue()) {
            isRunningOrHasQueue = true;
        }

        if (isRunningOrHasQueue) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.");
        }
        return ReturnT.SUCCESS;
    }

    public ReturnT<String> run(TriggerParam triggerParam){

        // load old：jobHandler + jobThread
        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
        JobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
        String removeOldReason = null;

        // valid：jobHandler + jobThread
        JobTypeEnum jobTypeEnum = JobTypeEnum.match(triggerParam.getJobType());

        // java bean模式
        if(JobTypeEnum.JOB_TYPE_BEAN == jobTypeEnum){
            // new jobhandler
            JobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getJobHandler());

            // valid old jobThread
            if (jobThread!=null && jobHandler != newJobHandler) {
                // change handler, need kill old thread
                removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                jobHandler = newJobHandler;
                if (jobHandler == null) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getJobHandler() + "] not found.");
                }
            }

        } else if (JobTypeEnum.JOB_TYPE_SQL == jobTypeEnum) {

            // valid old jobThread
            if (jobThread != null && !(jobThread.getHandler() instanceof SQLJobHandler && ((SQLJobHandler) jobThread.getHandler()).getJobSourceUpdateTime()==triggerParam.getJobSourceUpdateTime() )) {
                // change handler or gluesource updated, need kill old thread
                removeOldReason = "change job source or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                try {
                    jobHandler = new SQLJobHandler(jdbcTemplate,triggerParam.getJobSource(),triggerParam.getJobSourceUpdateTime());
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
                }
            }
        } else {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "jobType[" + triggerParam.getJobType() + "] is not valid.");
        }

        // executor block strategy
        if (jobThread != null) {
            ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getJobBlockStrategy(), null);
            if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
                // discard when running
                if (jobThread.isRunningOrHasQueue()) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect："+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
                }
            } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
                // kill running jobThread
                if (jobThread.isRunningOrHasQueue()) {
                    removeOldReason = "block strategy effect：" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();

                    jobThread = null;
                }
            } else {
                // just queue trigger
            }
        }

        // replace thread (new or exists invalid)
        if (jobThread == null) {
            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
        }

        // push data to queue
        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
        return pushResult;
    }

    public ReturnT<String> kill(KillParam killParam){
        // kill handlerThread, and create new one
        JobThread jobThread = XxlJobExecutor.loadJobThread(killParam.getJobId());
        if (jobThread != null) {
            XxlJobExecutor.removeJobThread(killParam.getJobId(), "scheduling center kill job.");
            return ReturnT.SUCCESS;
        }

        return new ReturnT<String>(ReturnT.SUCCESS_CODE, "job thread already killed.");
    }

    public ReturnT<LogResult> log(LogParam logParam){
        // log filename: logPath/yyyy-MM-dd/9999.log
        String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logParam.getLogDateTim()), logParam.getLogId());

        LogResult logResult = XxlJobFileAppender.readLog(logFileName, logParam.getFromLineNum());
        return new ReturnT<LogResult>(logResult);
    }
}
